/*
 * UserNet  --  TCP/IP implementation in userspace
 * Copyright (C) 2010  Robert Millan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <error.h>
#include <stdio.h>
#include <stdlib.h>		/* malloc */
#include <string.h>		/* memcmp */
#include <netinet/in.h>		/* ntohs */

#include "unet/ether.h"
#include "unet/arp.h"
#include "unet/ip.h"

struct incoming_packet_args
{
  struct unet_device *device;
  const uint8_t *packet;
  uint32_t len;
};
static void *
incoming_packet (void *a)
{
  struct incoming_packet_args *args = a;
  unet_ip_incoming_packet (args->device, args->packet, args->len);
  free (args);
  return NULL;
}

void
unet_ethernet_incoming_packet (struct unet_device *ud, const uint8_t *packet, const uint32_t len)
{
  const struct ether_header *header = (void *) packet;
  const uint8_t *payload = packet + sizeof (struct ether_header);
  const size_t payload_len = len - sizeof (*header);

  if (len < sizeof (*header))
    {
      printf ("invalid packet\n");
      return;
    }

  /* Process packet either when its destination MAC address is the broadcast
     address, or when it matches ours.  */
  if (memcmp (header->ether_dhost, ud->mac_addr, ETH_ALEN) != 0
      && memcmp (header->ether_dhost, broadcast_mac_addr, ETH_ALEN) != 0)
    return;

  printf ("\nIncoming ethernet packet from LAN, with size = %u\n", len);

  switch (ntohs (header->ether_type))
    {
    case ETHERTYPE_ARP:
      unet_arp_incoming_packet (ud, payload, payload_len);
      break;
    case ETHERTYPE_IP:
      {
	struct incoming_packet_args *args = malloc (sizeof (*args));
	args->device = ud;
	args->packet = payload;
	args->len = payload_len;
	pthread_t thread;
	pthread_create (&thread, NULL, incoming_packet, args);
      }
      break;
    }
}

void
unet_ethernet_send_packet (int type, const uint8_t mac_addr[ETH_ALEN], struct unet_device *device,
			   const uint8_t *packet, const uint32_t len)
{
  uint32_t ether_len = sizeof (struct ether_header) + len;
  uint8_t *ether_packet = malloc (ether_len);
  if (! ether_packet)
    return;

  struct ether_header *ether_header = (void *) ether_packet;

  /* Source address.  */
  memcpy (ether_header->ether_shost, device->mac_addr, ETH_ALEN);

  /* Destination address.  */
  memcpy (ether_header->ether_dhost, mac_addr, ETH_ALEN);

  /* Payload type.  */
  ether_header->ether_type = htons (type);

  /* Payload.  */
  memcpy (ether_packet + sizeof (*ether_header), packet, len);

  device->driver.send_packet (device, ether_packet, ether_len);

  free (ether_packet);
}

struct unet_datalink unet_ether_datalink =
  {
    .name = "Ethernet",
    .send_packet = unet_ethernet_send_packet,
    .get_mac = unet_arp_get_mac,
  };
